%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Module summary
%%
%% Main authors: Kristian, Olle
%% Progress to usefulness: 60%
%%
%% Implementation of a key-value store, with storage nodes,
%% and a central server that acts as a hub and communicates with clients.
%% Contains a setup procedure that creates the central server and nodes.
%%
%% Client protocol: Temporary message passing solution.
%%
%% The module uses a faked hash function for choosing the node
%% responsible for a key. 
%% The has function is returning values between 0 and 100. 
%%
%%%%%%%%%%%%%%%%%%%%%%%


-module(kvstore).
-compile(export_all).

%test_use_case() ->
%   DB = spawn(fun() -> kvstore:init([1,30,60,80,100],[]) end).
%   Adds the key/value to node 30:
%   DB ! {doput,{"apa","bepa"}}.
%   Adds the key/value to node 60:
%   DB ! {doput,{"kool","repa"}}.
%   Gets value of key "apa" from node 30:
%   DB ! {doget, "apa"}.
%   Gets value of key "kool" from node 60:
%   DB ! {doget, "kool"}.
start(Params) ->
   Conn = net_adm:ping(lists:nth(1,Params)),
   case Conn of
      pong ->
         io:fwrite("Database: Successfully connected to node network!\n");
      _ ->
         io:fwrite("Database: Couldn't connect to node network...\n")
   end,
   erlang:register(key_value_store,
                   spawn(fun() -> kvstore:init([1,30,50,80,100],[]) end)
                  ).

% given a list of integers, create nodes with that
% id number
init(Ids,IdsNodes) ->
   case IdsNodes of
      [] ->
            % TODO: store the Id number and node as a tuple
            % so that chord_node can compare the hashed key
            io:fwrite("Init the nodes!"),
            All = [{Id,spawn(fun() -> chord_node(Id,[],[]) end)} ||
            Id <- Ids ],
            send_others(All,All),
            init(Ids,All);
      _  -> receive
               {doput,{Key,Value}} ->
                  %put to the right node
                  io:fwrite("Call to put!\n"),
                  %delegate the put action to the first node
                  io:fwrite("Hash to\n"),
                  io:format("~.10#~n", [bounded_hash(Key)]),
                  Node = get_right_node(bounded_hash(Key),IdsNodes),
                  Node ! {doput,{Key,Value}},
                  %and let it delegate further if needed
                  init(Ids,IdsNodes);
               {doget,Key} ->
                  %get from the right node
                  io:fwrite("Call to get!\n"),
                  Node = get_right_node(bounded_hash(Key),IdsNodes),
                  Node ! {doget,Key},
                  %delegate the get action to the first node
                  %and let it delegate further if needed
                  init(Ids,IdsNodes)
            end
   end.

%send the list of all collected nodes
%to all collected nodes
%So, this isn't very dynamic, just sends
%all nodes to all nodes
send_others([],_) -> ok;
send_others([{_,Node}|Xs],All)
   -> Node ! {get_others,All},
      send_others(Xs,All).

% bounded between 0 and 100
bounded_hash(Str) -> hash_str(Str) rem 100.

% works because erlang translates
% characters to ints 
hash_str([])     -> 0;
hash_str([X|Xs]) -> X + hash_str(Xs).

% process that has an id between 0 and 100
% and also a list of
% 
% Store is a list of [{Key,Value}]
chord_node(Id,Others,Store) ->
   receive
      %kill the node
      die -> io:fwrite("Quit!\n");
      %Simple to inject all other nodes
      %to this node
      {get_others,NewOthers}
         -> chord_node(Id,NewOthers,Store);
      {doput,{Key,Value}}
         ->
            % find out who to forward to
            io:fwrite("Id number of node to put in\n"),
            io:format("~.10#~n", [Id]),
            chord_node(Id,Others,Store++[{Key,Value}]);
      {doget,Key}
         -> io:fwrite("Id number of node\n"),
            io:format("~.10#~n", [Id]),
            print_all([V || {K,V} <- Store, K == Key]),
            chord_node(Id,Others,Store)
   end.

print_all([])     -> io:fwrite("----");
print_all([X|Xs]) -> io:fwrite("Result: " ++ X ++ "\n"),
                     print_all(Xs).

% given a key, hash it and
% return the right node
%
% use case:
% kvstore:get_right_node(23,[{1999,apa},{24,cepa},{95,bepa},{2000,depa}]).
%
get_right_node(HashedKey,IdNodePair) ->
   element( 2,
      lists:nth(1, lists:sort(
            [{Id,Node} || {Id,Node} <- IdNodePair,HashedKey=<Id]
         )
      )
   ).

get_right_node_chord(HashedKey) ->
   serveriface:rem_query(undefined, {find_predecessor, HashedKey}).


